<?php
namespace App\Core\Mysql;

use Exception;
use PDO;
use PDOException;
use PDOStatement;

class Db
{
    //连接数组
    public static array $connectArr = [];
    public static int $pingTime = 4;

    /**
     * 执行一个SQL，返回PDOStatement
     * @param string $sql
     * @param array $params
     * @param string $key
     * @return false|PDOStatement
     * @throws Exception
     */
    public static function query(string $sql,array $params = [],$key = 'db')
    {
        try {
            $pdo = static::getDb($key);
            if(empty($params)){
                return $pdo->query($sql);
            }
            $statement = $pdo->prepare($sql);
            if(empty($statement)){
                return false;
            }
            $statement->execute($params);
            return $statement;
        }catch (Exception $e){
            throw new Exception($e->getMessage()."--SQL=" . $sql);
        }
    }

    /**
     * 执行一个SQL，并返回一个结果集
     * @param string $sql
     * @param array $params
     * @param string $key
     * @throws Exception
     * @return array
     */
    public static function queryOne(string $sql,array $params = [],$key = 'db')
    {
        try {
            $pdo = static::getDb($key);
            if(empty($params)){
                $statement = $pdo->query($sql);
                if(empty($statement)){
                    return [];
                }
                return $statement->fetch();
            }
            $statement = $pdo->prepare($sql);
            if(empty($statement)){
                return [];
            }
            $statement->execute($params);
            return $statement->fetch();
        }catch (Exception $e){
            throw new Exception($e->getMessage()."--SQL=" . $sql);
        }
    }

    /**
     * 执行一个SQL，并返回所有结果集
     * @param string $sql
     * @param array $params
     * @param string $key
     * @throws Exception
     * @return array
     */
    public static function queryAll(string $sql,array $params = [],$key = 'db')
    {
        try {
            $pdo = static::getDb($key);
            if(empty($params)){
                $statement = $pdo->query($sql);
                if(empty($statement)){
                    return [];
                }
                return $statement->fetchAll();
            }
            $statement = $pdo->prepare($sql);
            if(empty($statement)){
                return [];
            }
            $statement->execute($params);
            return $statement->fetchAll();
        }catch (Exception $e){
            throw new Exception($e->getMessage()."--SQL=" . $sql);
        }
    }

    /**
     * 执行一个SQL，返回受影响行数
     * @param string $sql
     * @param array $params
     * @param string $key
     * @return int
     * @throws Exception
     */
    public static function exec(string $sql,array $params = [],$key = 'db')
    {
        try {
            $pdo = static::getDb($key);
            if(empty($params)){
                return $pdo->exec($sql) ?? 0;
            }
            $statement = $pdo->prepare($sql);
            if(empty($statement)){
                return 0;
            }
            $statement->execute($params);
            return $statement->rowCount();
        }catch (Exception $e){
            throw new Exception($e->getMessage()."--SQL=" . $sql);
        }
    }

    /**
     * 获取上次插入的ID
     * @param string $key
     * @return string
     * @throws Exception
     */
    public static function getLastId($key = 'db')
    {
        $pdo = static::getDb($key);
        return $pdo->lastInsertId();
    }

    /**
     * @param string $key
     * @throws Exception
     */
    public static function begin($key = 'db')
    {
        static::getDb($key)->beginTransaction();
    }

    /**
     * @param string $key
     * @throws Exception
     */
    public static function commit($key = 'db')
    {
        static::getDb($key)->commit();
    }

    /**
     * @param string $key
     * @throws Exception
     */
    public static function back($key = 'db')
    {
        static::getDb($key)->rollBack();
    }

    /**
     * @param string $key
     * @return PDO
     * @throws Exception
     */
    public static function getDb($key = 'db')
    {
        if(!isset(static::$connectArr[$key])){
            $pdo = static::connect($key);
            static::$connectArr[$key] = [
                'connect'  => $pdo,
                'ping'     => time()
            ];
        }
        if(time() - static::$connectArr[$key]['ping'] >= static::$pingTime){
            $isCanUse = static::ping(static::$connectArr[$key]['connect']);
            if(!$isCanUse){
                static::$connectArr[$key]['connect'] = null;
                unset(static::$connectArr[$key]);
                return static::getDb($key);
            }
            static::$connectArr[$key]['ping'] = time();
        }
        return static::$connectArr[$key]['connect'];
    }

    /**
     * @param string $key
     * @return PDO
     * @throws Exception
     */
    public static function connect($key = 'db')
    {
        $config = config();
        if(!isset($config[$key])){
            throw new Exception('数据库配置错误('.$key.'未配置)');
        }
        $dbConfig = $config[$key];
        if(empty($dbConfig['dsn'])){
            throw new Exception('数据库配置错误(DSN未配置)');
        }
        if(empty($dbConfig['username'])){
            throw new Exception('数据库配置错误(username未配置)');
        }
        if(empty($dbConfig['password'])){
            throw new Exception('数据库配置错误(password未配置)');
        }
        $op = [
            PDO::ATTR_STRINGIFY_FETCHES => false,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ];
        return new PDO($dbConfig['dsn'],$dbConfig['username'],$dbConfig['password'],$op);
    }

    /**
     * @param PDO $connect
     * @return bool
     * @throws Exception
     */
    public static function ping(PDO $connect)
    {
        try {
            $connect->getAttribute(PDO::ATTR_SERVER_INFO);
        }catch (PDOException $e){
            //执行重连
            if(strpos($e->getMessage(), 'server has gone away') !== false){
                return false;
            }
            //其他错误，抛出异常
            throw new Exception($e->getMessage());
        }
        return true;
    }
}
